﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;

namespace InfoPathTranslate
{


    public enum ExpNodeType
    {
        Ref,
        Value,
        Op,
        Logical,
        Function
    }

    public enum ExpOpType
    {
        Equels,
        NotEquels,
        GreaterThan,
        NotGreaterThan,
        LessThan,
        NotLessThan,
        Exists,
        NotExists,
        Null,
        NotNull,
        Contains,
        NotContains,
        StartWith,
        NotStartWith,
        Match,
        NotMatch
    }

    public enum ExpLogicalType
    {
        And,
        Or
    }

    public interface ExpNode
    {
        ExpNodeType GetNodeType();
        bool Test(InfoPathData data);
        InfoPathField GetValue(InfoPathData data);
        InfoPathFieldType GetFieldType(InfoPathData data);
    }

    public class ExpValueNode : ExpNode
    {
        private String value;
        private InfoPathFieldType type = InfoPathFieldType.String;

        public ExpNodeType GetNodeType()
        {
            return ExpNodeType.Value;
        }

        public ExpValueNode(String value)
        {
            this.value = value;
        }

        public bool Test(InfoPathData data)
        {
            return true;
        }

        public InfoPathField GetValue(InfoPathData data)
        {
            InfoPathString f = new InfoPathString(this.value);
            return f;
        }

        public InfoPathFieldType GetFieldType(InfoPathData data)
        {
            return InfoPathFieldType.String;
        }

        public void SetValue(String v)
        {
            this.value = v;
        }

        public String GetValue()
        {
            return this.value;
        }

        public InfoPathFieldType GetFieldType()
        {
            return this.type;
        }

        public void SetFieldType(InfoPathFieldType t)
        {
            this.type = t;
        }
    }

    public class ExpRefNode : ExpNode
    {
        private String valueName;

        public ExpNodeType GetNodeType()
        {
            return ExpNodeType.Ref;
        }

        public ExpRefNode(String name)
        {
            this.valueName = name;
        }

        public bool Test(InfoPathData data)
        {
            InfoPathField f = data.GetField(this.valueName);
            if (f.GetFieldType() == InfoPathFieldType.Boolean)
            {
                return ((InfoPathBoolean)f).GetBooleanValue();
            }
            else
            {
                return true;
            }
        }

        public InfoPathField GetValue(InfoPathData data)
        {
            InfoPathField f = data.GetField(this.valueName);
            return f;
        }


        public InfoPathFieldType GetFieldType(InfoPathData data)
        {
            InfoPathField f = data.GetField(this.valueName);
            return f.GetFieldType();
        }

        public String GetValueName()
        {
            return this.valueName;
        }

        public void SetValueName(String vn)
        {
            this.valueName = vn;
        }
    }

    public class ExpOpNode : ExpNode
    {
        private ExpOpType op;
        private ExpNode lnode;
        private ExpNode rnode;


        public ExpNodeType GetNodeType()
        {
            return ExpNodeType.Op;
        }

        public bool Test(InfoPathData data)
        {
            switch (this.op)
            {
                case ExpOpType.Equels:
                    return this.lnode.GetValue(data).Equels(this.rnode.GetValue(data));
                case ExpOpType.NotEquels:
                    return this.lnode.GetValue(data).NotEquels(this.rnode.GetValue(data));
            }
            return false;

        }

        public InfoPathField GetValue(InfoPathData data)
        {
            InfoPathBoolean f = new InfoPathBoolean(this.Test(data));
            return f;
        }

        public InfoPathFieldType GetFieldType(InfoPathData data)
        {
            return InfoPathFieldType.Boolean;
        }

        public void SetLeftOperand(ExpNode n)
        {
            this.lnode = n;
        }

        public void SetRightOperand(ExpNode n)
        {
            this.rnode = n;
        }

        public void SetOperands(ExpNode left, ExpNode right)
        {
            this.lnode = left;
            this.rnode = right;
        }

        public void SetOp(ExpOpType type)
        {
            this.op = type;
        }

        public ExpNode getLeftOperand()
        {
            return this.lnode;
        }

        public ExpNode getRightOperand()
        {
            return this.rnode;
        }

        public ExpOpType getOperator()
        {
            return this.op;
        }
    }

    public class ExpLogicalNode : ExpNode
    {
        private ExpLogicalType op;
        private ExpNode lnode;
        private ExpNode rnode;


        public ExpNodeType GetNodeType()
        {
            return ExpNodeType.Op;
        }

        public bool Test(InfoPathData data)
        {
            switch (this.op)
            {
                case ExpLogicalType.And:
                    return this.lnode.Test(data) && this.rnode.Test(data);
                case ExpLogicalType.Or:
                    return this.lnode.Test(data) || this.rnode.Test(data);
            }
            return false;

        }

        public InfoPathField GetValue(InfoPathData data)
        {
            InfoPathBoolean f = new InfoPathBoolean(this.Test(data));
            return f;
        }

        public InfoPathFieldType GetFieldType(InfoPathData data)
        {
            return InfoPathFieldType.Boolean;
        }


        public void SetLeftOperand(ExpNode n)
        {
            this.lnode = n;
        }

        public void SetRightOperand(ExpNode n)
        {
            this.rnode = n;
        }

        public void SetOperands(ExpNode left, ExpNode right)
        {
            this.lnode = left;
            this.rnode = right;
        }

        public void SetOp(ExpLogicalType type)
        {
            this.op = type;
        }

        public ExpNode getLeftOperand()
        {
            return this.lnode;
        }

        public ExpNode getRightOperand()
        {
            return this.rnode;
        }

        public ExpLogicalType getOperator()
        {
            return this.op;
        }
    }

    public class ExpFunctionNode : ExpNode
    {
        private String name;
        private ArrayList args;

        public ExpNodeType GetNodeType()
        {
            return ExpNodeType.Function;
        }

        public bool Test(InfoPathData data)
        {
            InfoPathField f = this.GetValue(data);
            if (f == null)
            {
                return false;
            }
            if (f.GetFieldType() == InfoPathFieldType.Boolean)
            {
                return ((InfoPathBoolean)f).GetBooleanValue();
            }
            else
            {
                return true;
            }
        }

        public InfoPathField GetValue(InfoPathData data)
        {
            if (this.args != null)
            {
                InfoPathField[] argValues = new InfoPathField[this.args.Count];
                for (int i = 0; i < this.args.Count; i++)
                {
                    argValues[i] = ((ExpNode)this.args[i]).GetValue(data);
                }
                return XsltFunctionManager.ExecuteFunction(this.name, argValues);
            }
            else
            {
                return XsltFunctionManager.ExecuteFunction(this.name, null);
            }
        }

        public InfoPathFieldType GetFieldType(InfoPathData data)
        {
            return XsltFunctionManager.GetReturnType(this.name);
        }

        public void AddArg(ExpNode n)
        {
            if (this.args == null)
            {
                this.args = new ArrayList();
            }
            this.args.Add(n);
        }

        public ArrayList GetArgs()
        {
            return this.args;
        }

        public String GetName()
        {
            return this.name;
        }

        public void SetName(String n)
        {
            this.name = n;
        }
    }


    public class InfoPathExpression
    {
        private ExpNode root;

        public bool Test(InfoPathData data)
        {
            if (this.root == null)
            {
                return false;
            }
            return this.root.Test(data);
        }

        public String GetExpJs(InfoPathData data, ArrayList references)
        {
            return this.GetExpNodeJs(this.root, data, references);
        }

        public void SetRootNode(ExpNode n)
        {
            this.root = n;
        }

        public ExpNode GetRootNode()
        {
            return this.root;
        }

        private String GetExpNodeJs(ExpNode node, InfoPathData data, ArrayList references)
        {
            switch (node.GetNodeType())
            {
                case ExpNodeType.Value:
                    ExpValueNode vn = (ExpValueNode)node;
                    return "\"" + vn.GetValue() + "\"";

                case ExpNodeType.Ref:
                    ExpRefNode rn = (ExpRefNode)node;
                    references.Add(rn.GetValueName());
                    return "getField(\"" + rn.GetValueName() + "\")";

                case ExpNodeType.Op:
                    ExpOpNode on = (ExpOpNode)node;
                    ExpNode lv = on.getLeftOperand();
                    ExpNode rv = on.getRightOperand();
                    ExpOpType ot = on.getOperator();
                    if (ot == ExpOpType.Equels)
                    {
                        return this.GetExpNodeJs(lv, data, references) + " == " + this.GetCastJs(this.GetExpNodeJs(rv, data, references), rv.GetFieldType(data), lv.GetFieldType(data));
                    }
                    else if (ot == ExpOpType.NotEquels)
                    {
                        return this.GetExpNodeJs(lv, data, references) + " != " + this.GetCastJs(this.GetExpNodeJs(rv, data, references), rv.GetFieldType(data), lv.GetFieldType(data));
                    }
                    else if (ot == ExpOpType.GreaterThan)
                    {
                        return this.GetExpNodeJs(lv, data, references) + " > " + this.GetCastJs(this.GetExpNodeJs(rv, data, references), rv.GetFieldType(data), lv.GetFieldType(data));
                    }
                    else if (ot == ExpOpType.LessThan)
                    {
                        return this.GetExpNodeJs(lv, data, references) + " < " + this.GetCastJs(this.GetExpNodeJs(rv, data, references), rv.GetFieldType(data), lv.GetFieldType(data));
                    }
                    else if (ot == ExpOpType.NotGreaterThan)
                    {
                        return this.GetExpNodeJs(lv, data, references) + " <= " + this.GetCastJs(this.GetExpNodeJs(rv, data, references), rv.GetFieldType(data), lv.GetFieldType(data));
                    }
                    else if (ot == ExpOpType.NotLessThan)
                    {
                        return this.GetExpNodeJs(lv, data, references) + " >= " + this.GetCastJs(this.GetExpNodeJs(rv, data, references), rv.GetFieldType(data), lv.GetFieldType(data));
                    }
                    return "";
                case ExpNodeType.Function:
                    ExpFunctionNode fn = (ExpFunctionNode)node;
                    String s = "xslt_" + fn.GetName() + "(";
                    ArrayList args = fn.GetArgs();
                    if (args != null)
                    {
                        for (int i = 0; i < args.Count; i++)
                        {
                            if (i > 0)
                            {
                                s = s + ",";
                            }
                            s = s + this.GetExpNodeJs((ExpNode)args[i], data, references);
                        }
                    }
                    s += ")";
                    return s;

                case ExpNodeType.Logical:
                    return "";
            }
            return "";
        }

        private String GetCastJs(String operand, InfoPathFieldType fromType, InfoPathFieldType toType)
        {
            if (fromType == toType)
            {
                return operand;
            }

            if (fromType == InfoPathFieldType.String)
            {
                if (toType == InfoPathFieldType.Boolean)
                {
                    return "castStringToBoolean(" + operand + ")";
                }
            }
            else if (fromType == InfoPathFieldType.Boolean)
            {
                if (toType == InfoPathFieldType.String)
                {
                    return "castBooleanToString(" + operand + ")";
                }
            }

            return operand;
        }

    }

    public class InfoPathExpressionParser
    {
        private String exp;
        private String str;
        private static Regex rgxDirectValue = null;
        private static Regex rgxRefValue = null;
        private static Regex rgxFunctionName = null;
        private static Regex rgxOperator = null;

        public InfoPathExpressionParser(String exp)
        {
            this.exp = exp;
            if (rgxDirectValue == null)
            {
                rgxDirectValue = new Regex("^\\\"([^\\\"]*)\\\"");
            }
            if (rgxRefValue == null)
            {
                rgxRefValue = new Regex("^[a-zA-Z](\\w|\\:|\\\\|_|\\-\\.)*");
            }
            if (rgxFunctionName == null)
            {
                rgxFunctionName = new Regex("^([a-zA-Z])+\\(");
            }
            if (rgxOperator == null)
            {
                rgxOperator = new Regex("\\=|\\>|\\>\\=|\\<\\=|\\!\\=");
            }
        }

        public InfoPathExpression Parse()
        {
            this.str = exp.Trim();
            ExpNode n = this.ParseSingleExp();
            InfoPathExpression ipe = new InfoPathExpression();
            ipe.SetRootNode(n);
            return ipe;
        }

        private ExpNode ParseSingleExp()
        {
            ExpNode lv = this.ParseOperand();
            if (lv == null)
            {
                return null;
            }
            ExpOpNode op = this.ParseOperator();
            if (op == null)
            {
                if (this.Finish())
                {
                    return lv;
                }
                else
                {
                    return null;
                }
            }
            ExpNode rv = this.ParseOperand();
            if (rv == null) return null;
            op.SetOperands(lv, rv);
            return op;
        }

        private ExpNode ParseOperand()
        {
            ExpNode n = null;
            String tk = this.GetToken(rgxDirectValue);
            if (tk != null)
            {
                String v = tk.Substring(1, tk.Length - 2);
                n = new ExpValueNode(v);
                return n;
            }

            tk = this.GetToken(rgxFunctionName);
            if (tk != null)
            {
                ExpFunctionNode fn = new ExpFunctionNode();
                fn.SetName(tk.Substring(0, tk.Length - 1));
                ExpNode arg = null;

                while (!this.GetChar(')'))
                {
                    arg = this.ParseOperand();
                    if (arg == null)
                    {
                        break;
                    }

                    fn.AddArg(arg);
                    if (!this.GetChar(','))
                    {
                        break;
                    }
                }
                return fn;

            }


            tk = this.GetToken(rgxRefValue);
            if (tk != null)
            {
                n = new ExpRefNode(tk);
                return n;
            }
            return null;
        }

        private ExpOpNode ParseOperator()
        {
            String op = this.GetToken(rgxOperator);
            if (op == null)
            {
                return null;
            }
            ExpOpNode n = new ExpOpNode();
            if (op.Equals("="))
            {
                n.SetOp(ExpOpType.Equels);
            }
            else if (op.Equals(">"))
            {
                n.SetOp(ExpOpType.GreaterThan);
            }
            else if (op.Equals("<"))
            {
                n.SetOp(ExpOpType.LessThan);
            }
            else if (op.Equals(">="))
            {
                n.SetOp(ExpOpType.NotLessThan);
            }
            else if (op.Equals("<="))
            {
                n.SetOp(ExpOpType.NotGreaterThan);
            }
            else if (op.Equals("!="))
            {
                n.SetOp(ExpOpType.NotEquels);
            }
            else
            {
                return null;
            }

            return n;
        }

        private ExpLogicalNode ParseLogicalNode()
        {
            return null;
        }


        private bool Finish()
        {
            return (this.str.Length == 0);
        }

        private String GetToken(Regex rgx)
        {
            Match m = rgx.Match(this.str);
            if (m.Success)
            {
                String v = this.str.Substring(m.Index, m.Length);
                this.str = this.str.Substring(m.Length).TrimStart();
                return v;
            }
            return null;
        }

        private bool GetChar(char c)
        {
            if (this.str[0] == c)
            {
                this.str = this.str.Substring(1).TrimStart();
                return true;
            }
            return false;
        }
    }

    
}
